﻿using System;
using System.Linq.Expressions;

namespace NMA.Infrastructure.LambdaExpression
{
    /// <summary>
    /// http://www.lostechies.com/blogs/chad_myers/archive/2008/10/27/composing-statements-with-expression-trees-in-net-3-0.aspx
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TDTO"></typeparam>
    public class LambdaExpressionHelper<TEntity, TDTO>
    {
        /// <summary>
        /// Convert from TEntity to TDTO object
        /// </summary>
        /// <param name="input">the Expression Tree</param>
        /// <param name="type">Type that you contain the identity column name</param>
        /// <returns>Expression Tree that is converted to TEntity</returns>
        public static Expression<Func<TEntity, bool>> Convert(Expression<Func<TDTO, bool>> input, Type type)
        {            
            ParameterExpression param = (ParameterExpression)input.Parameters[0];
            BinaryExpression operation = (BinaryExpression)input.Body;            
            
            if(operation.Left.NodeType != ExpressionType.Call)            
            {
                if (operation.Right.GetType() == typeof(ConstantExpression))                               
                    return RightWithConstant(input, type);

                return RightWithMethod(input, type);
            }

            return LeftWithMethod(input, type);            
        }

        /// <summary>
        /// The Lambda Expression with method same as
        ///  x => x.Name == "testname"
        /// </summary>
        /// <param name="input">the Expression Tree</param>
        /// <param name="type">Type that you contain the identity column name</param>
        /// <returns>Expression Tree that is converted to TEntity</returns>
        private static Expression<Func<TEntity, bool>> RightWithConstant(Expression<Func<TDTO, bool>> input, Type type)
        {
            ParameterExpression param = (ParameterExpression)input.Parameters[0];
            BinaryExpression operation = (BinaryExpression)input.Body;

            MemberExpression left = (MemberExpression)operation.Left;            
            ConstantExpression right = (ConstantExpression)operation.Right;

            // build the Expression Tree
            ParameterExpression paramA = Expression.Parameter(type, param.Name);
            ConstantExpression paramB = Expression.Constant(right.Value);            
            
            MemberExpression member = Expression.Property(paramA, type.GetProperty(left.Member.Name));            

            BinaryExpression binaryEx = Expression.Equal(member, paramB);
            Expression<Func<TEntity, bool>> lambda = Expression.Lambda<Func<TEntity, bool>>(binaryEx, new ParameterExpression[] { paramA });            

            return lambda;            
        }

        /// <summary>
        /// The Lambda Expression with method same as
        ///  x => x.CreatedDate.CompareTo(DateTime.Now) == 0
        /// </summary>
        /// <param name="input">the Expression Tree</param>
        /// <param name="type">Type that you contain the identity column name</param>
        /// <returns>Expression Tree that is converted to TEntity</returns>
        private static Expression<Func<TEntity, bool>> LeftWithMethod(Expression<Func<TDTO, bool>> input, Type type)
        {
            // Trying to split tokens out
            // x
            ParameterExpression param = (ParameterExpression)input.Parameters[0];
            // x.CreatedDate.CompareTo(DateTime.Now) == 0
            BinaryExpression operation = (BinaryExpression)input.Body;
            // x.CreatedDate.CompareTo(DateTime.Now)
            MethodCallExpression left = (MethodCallExpression)operation.Left;
            // 0
            ConstantExpression right = (ConstantExpression)operation.Right;
            // x.CreatedDate
            MemberExpression leftMemberEx = (MemberExpression)left.Object;
            // x
            ParameterExpression leftParamEx = (ParameterExpression)leftMemberEx.Expression;
            // CompareTo
            var method = left.Method;
            // DateTime.Now
            var arguments = left.Arguments;

            // build the Expression Tree
            ParameterExpression paramA = Expression.Parameter(type, leftParamEx.Name);
            // x.CreatedDate
            MemberExpression member = Expression.Property(paramA, type.GetProperty(leftMemberEx.Member.Name));
            // built method call
            var methodCall = Expression.Call(member, method, arguments[0]);
            // build the binary expression
            BinaryExpression binaryEx = Expression.Equal(methodCall, right);
            // build the lambda expression
            Expression<Func<TEntity, bool>> lambda = Expression.Lambda<Func<TEntity, bool>>(binaryEx, new ParameterExpression[] { paramA });
            
            return lambda;
        }

        /// <summary>
        /// The Lambda Expression with method same as
        /// x => x.Id == Guid.NewGuid()
        /// </summary>
        /// <param name="input">the Expression Tree</param>
        /// <param name="type">Type that you contain the identity column name</param>
        /// <returns>Expression Tree that is converted to TEntity</returns>
        private static Expression<Func<TEntity, bool>> RightWithMethod(Expression<Func<TDTO, bool>> input, Type type)
        {
            // x
            ParameterExpression param = (ParameterExpression)input.Parameters[0];
            // x.Id == Guid.NewGuid()
            BinaryExpression operation = (BinaryExpression)input.Body;
            // x.Id
            var left = (MemberExpression)operation.Left;
            // Guid.NewGuid()
            var right = operation.Right;
            // x
            var leftParameterEx = (ParameterExpression)left.Expression;

            //build expression            
            ParameterExpression paramA = Expression.Parameter(type, leftParameterEx.Name);
            // x.Id
            MemberExpression member = Expression.Property(paramA, type.GetProperty(left.Member.Name));            
            // build the binary expression
            BinaryExpression binaryEx = Expression.Equal(member, right);
            // build the lambda expression
            Expression<Func<TEntity, bool>> lambda = Expression.Lambda<Func<TEntity, bool>>(binaryEx, new ParameterExpression[] { paramA });

            return lambda;            
        }
    }
}